//===============================================================================
// Microsoft patterns & practices
// Guidance Explorer Offline Client
//===============================================================================
// Copyright  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================



using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Xml;
using GuidanceExplorer.Model;
using GuidanceExplorer.Schemas;

namespace GuidanceExplorer.Services
{
    public class AsyncLoadLibrariesService : IAsyncLoadLibrariesService
    {
        #region IAsyncLoadLibrariesService Members

        /// <summary>
        /// Process the items across the entire Guidance.
        /// </summary>
        /// <param name="guidance">The root guidance model.</param>
        /// <param name="bw">Step for the progress bar.</param>
        public void ProcessGuidance(Guidance guidance, BackgroundWorker bw)
        {
            //Create a warning list.
            List<string> warnings = new List<string>();

            //Process all the libraries.
            foreach (KeyValuePair<string, Library> library in guidance.Libraries)
            {
                //Process all the items in the library.
                ProcessLibrary(library.Value, bw, warnings);
            }
        }

        /// <summary>
        /// Process a single library.
        /// </summary>
        /// <param name="library"></param>
        /// <param name="bw"></param>
        /// <param name="warnings"></param>
        public void ProcessLibrary(Library library, BackgroundWorker bw, List<string> warnings)
        {
            GuidanceItemCollection newItems = GuidanceItemCollection.New();
            string indexName = library.IndexFileName;
            string backupIndexName = indexName;

            if (!File.Exists(indexName))
            {
                //Create the index and a backup.
                CreateLibraryIndex(library, bw, indexName);
                CreateBackupIndex(indexName);
            }

            //Read from the index.
            try
            {
                ProcessLibraryIndex(library, bw, newItems, indexName);
            }
            catch (Exception)
            {
                UseBackupIndex(indexName);
                ProcessLibraryIndex(library, bw, newItems, indexName);
            }

            //if we got this far then the index was processed without an exception so create a backup
            CreateBackupIndex(indexName);

            if (library.Items.Count != newItems.Count)
            {
                //TODO: just a fix up situation.
            }

            //Clear the items list
            library.Items.Clear();

            //Add the new items list.
            foreach (GuidanceItem item in newItems)
            {
                library.Items.Add(item.Id, item);
            }

        }

        /// <summary>
        /// Create the index for the library.
        /// </summary>
        /// <param name="library"></param>
        /// <param name="bw"></param>
        /// <param name="indexName"></param>
        private void CreateLibraryIndex(Library library, BackgroundWorker bw, string indexName)
        {
            string newPath = string.Empty;

            //Create the library folder if it does not exist.
            if (!Directory.Exists(Path.GetDirectoryName(indexName)))
            {
                Directory.CreateDirectory(Path.GetDirectoryName(indexName));
            }

            using (FileStream indexFileStream = new FileStream(indexName, FileMode.CreateNew, FileAccess.Write))
            using (XmlTextWriter tw = new XmlTextWriter(indexFileStream, System.Text.Encoding.UTF8))
            {
                tw.WriteStartElement("index", SchemaHelper.GuidanceItemNamespace);

                tw.Formatting = Formatting.Indented;
                foreach (GuidanceItem item in library.Items)
                {
                    //set the containing library
                    item.Library = library;

                    //Chech if the item is a GuidanceItemReference.
                    GuidanceItemFileReference reference = item as GuidanceItemFileReference;
                    if (reference == null)
                    {
                        continue;
                    }

                    //Convert into a full path.
                    if (library.FileSubscribed)
                    {
                        newPath = Path.GetFullPath(Path.Combine(library.OriginalSourceFileInfo.DirectoryName, reference.FileName));
                    }
                    else
                    {
                        newPath = Path.GetFullPath(Path.Combine(library.SourceFileInfo.DirectoryName, reference.FileName));
                    }

                    if (!File.Exists(newPath))
                    {
                        continue;
                    }

                    //Open the item file.
                    using (FileStream fileStream = new FileStream(newPath, FileMode.Open, FileAccess.Read))
                    using (XmlReader reader = XmlTextReader.Create(fileStream))
                    {
                        while (reader.Read())
                        {
                            if (reader.Name == SchemaHelper.GuidanceItem.Element && reader.NodeType == XmlNodeType.Element && reader.NamespaceURI == SchemaHelper.GuidanceItemNamespace)
                            {
                                tw.WriteStartElement(reader.LocalName);
                                tw.WriteAttributeString(SchemaHelper.GuidanceItem.FilePath, reference.FileName);
                                if (reader.MoveToFirstAttribute())
                                {
                                    do
                                    {
                                        if (reader.Name != "xmlns")
                                        {
                                            if (!String.Equals(reader.Name, SchemaHelper.GuidanceItem.FilePath, StringComparison.InvariantCultureIgnoreCase))
                                            {
                                                tw.WriteAttributeString(reader.Name, reader.Value);
                                            }
                                        }
                                    } while (reader.MoveToNextAttribute());
                                }

                                tw.WriteEndElement();

                                //Only the header is needed.
                                break;
                            }
                        }
                    }
                    //Update the progress bar.
                    if (bw != null)
                    {
                        bw.ReportProgress(1);
                    }
                }
                tw.WriteEndElement(); //index
            }
        }

        /// <summary>
        /// backup current index file.
        /// </summary>
        /// <param name="indexName">location of current index</param>
        private void CreateBackupIndex(string indexName)
        {
            string backupIndexName = indexName;
            if (File.Exists(indexName))
            {
                backupIndexName = indexName + ".bak";
                File.Copy(indexName, backupIndexName, true);
            }
        }

        /// <summary>
        /// Replace the current index file with the backup.
        /// </summary>
        /// <param name="backupIndexName">location of backup index</param>
        private void UseBackupIndex(string indexName)
        {
            string backupIndexName = indexName + ".bak";
            if (File.Exists(backupIndexName))
            {
                File.Copy(backupIndexName, backupIndexName.Remove(backupIndexName.Length - 4, 4), true);
            }
        }

        /// <summary>
        /// Read the contents of the index and populate the internal metadata structures.
        /// </summary>
        /// <param name="library"></param>
        /// <param name="bw"></param>
        /// <param name="newItems"></param>
        /// <param name="indexName"></param>
        private void ProcessLibraryIndex(Library library, BackgroundWorker bw, GuidanceItemCollection newItems, string indexName)
        {
            using (FileStream fileStream = new FileStream(indexName, FileMode.Open, FileAccess.Read))
            using (XmlReader reader = XmlTextReader.Create(fileStream))
            {
                while (reader.Read())
                {
                    if (SchemaHelper.IsGuidanceItemNode(reader, SchemaHelper.GuidanceItem.Element))
                    {
                        GuidanceItemFileReference gifr = GuidanceItemFileReference.New();
                        ProcessGuidanceItemMetadataElement(gifr, reader);

                        GuidanceItemFileReference reference = null;
                        try
                        {
                            reference = library.Items[gifr[SchemaHelper.GuidanceItem.FilePath]] as GuidanceItemFileReference;
                        }
                        catch { /*I don't care*/ }
                        if (reference == null)
                        {
                            reference = gifr;
                            reference.FileName = (string)gifr[SchemaHelper.GuidanceItem.FilePath];
                        }

                        ProcessGuidanceItemMetadataElement(reference, reader);

                        //set the containing library
                        reference.Library = library;

                        ////Convert into a full path.
                        //if (library.FileSubscribed)
                        //{
                        //    reference.FileName = Path.GetFullPath(Path.Combine(library.OriginalSourceFileInfo.DirectoryName, reference.FileName));
                        //}
                        //else
                        //{
                        //    reference.FileName = Path.GetFullPath(Path.Combine(library.SourceFileInfo.DirectoryName, reference.FileName));
                        //}

                        if (reference.GuidanceType != null)
                        {
                            //Add the item to the new collection
                            if (!newItems.ContainsKey(reference.Id))
                            {
                                newItems.Add(reference.Id, reference);
                            }
                            reference[GuidanceItem.TypeField] = reference.GuidanceType.DisplayName;
                        }

                        //Update the progress bar.
                        if (bw != null)
                        {
                            bw.ReportProgress(1);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Process the file for the item, load the metadata and read the contents.
        /// </summary>
        /// <param name="library">The library instance.</param>
        /// <param name="reference">The guidance item file reference to process</param>
        public void ProcessGuidanceItemFile(GuidanceItemFileReference reference)
        {
            using (FileStream fileStream = new FileStream(reference.FileName, FileMode.Open, FileAccess.Read))
            using (XmlReader reader = XmlTextReader.Create(fileStream))
            {
                ProcessGuidanceItemFileReader(reference, reader);
            }
        }

        /// <summary>
        /// Process the metadata for an item by reading its xml representation.
        /// </summary>
        /// <param name="library"></param>
        /// <param name="reference"></param>
        /// <param name="reader"></param>
        /// <returns></returns>
        private GuidanceItemFileReference ProcessGuidanceItemFileReader(GuidanceItemFileReference reference, XmlReader reader)
        {
            while (reader.Read())
            {
                if (SchemaHelper.IsGuidanceItemNode(reader, SchemaHelper.GuidanceItem.Element))
                {
                    ProcessGuidanceItemMetadataElement(reference, reader);
                }
                else if (SchemaHelper.IsGuidanceItemNode(reader, SchemaHelper.GuidanceItem.Images))
                {
                    ProcessGuidanceItemImages(reference, reader);
                }
                else if (SchemaHelper.IsGuidanceItemNode(reader, SchemaHelper.GuidanceItem.Content))
                {
                    ProcessGuidanceItemContent(reader.ReadSubtree());
                    if (string.IsNullOrEmpty(reference.CSSFile))
                    {
                        reference.CSSFile = ModelStorageService.DefaultCSSFileName;
                    }
                }
            }
            return reference;
        }

        private void ProcessGuidanceItemImages(GuidanceItemFileReference reference, XmlReader reader)
        {
            ModelStorageService storageService = new ModelStorageService();
            storageService.ConvertXmlReaderToImages(reference, reader);
        }

        private void ProcessGuidanceItemMetadataElement(GuidanceItemFileReference reference, XmlReader reader)
        {
            if (reader.MoveToFirstAttribute())
            {
                do
                {
                    if (reader.Name == SchemaHelper.GuidanceItem.Id)
                    {
                        reference.Id = reader.Value;
                    }
                    else if (reader.Name == SchemaHelper.GuidanceItem.Title)
                    {
                        reference.Title = reader.Value;
                    }
                    else if (reader.Name == SchemaHelper.GuidanceItem.Type)
                    {
                        reference.GuidanceType = GuidanceType.Get(reader.Value);
                    }

                    else if (reader.Name == SchemaHelper.GuidanceItem.CSSFile)
                    {
                        reference.CSSFile = reader.Value;
                    }
                    else if (reader.Name == SchemaHelper.GuidanceItem.Date)
                    {
                        reference.DateTime = DateTime.Parse(reader.Value, System.Globalization.DateTimeFormatInfo.InvariantInfo, System.Globalization.DateTimeStyles.AdjustToUniversal | System.Globalization.DateTimeStyles.AssumeUniversal);
                    }
                    else if (reader.Name != "xmlns")
                    {
                        reference[reader.Name] = reader.Value;
                    }
                } while (reader.MoveToNextAttribute());
            }
        }

        /// <summary>
        /// Read the content of the item.
        /// </summary>
        /// <param name="reader">The XmlReader positioned at the content node.</param>
        private void ProcessGuidanceItemContent(XmlReader reader)
        {
            while (reader.Read())
            {
                if (SchemaHelper.IsGuidanceItemNode(reader, SchemaHelper.GuidanceItem.Content))
                {
                    reader.Skip();
                }
            }
        }

        #endregion
    }
}

